package com.tmzn.service.impl;

import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tmzn.constant.IotConstant;
import com.tmzn.db.DataSource;
import com.tmzn.db.DataSourceType;
import com.tmzn.domain.TDevicePlan;
import com.tmzn.domain.TDevicePlanAddNewDTO;
import com.tmzn.domain.TPlanRecord;
import com.tmzn.entity.PortData;
import com.tmzn.entity.param.DeviceParam;
import com.tmzn.jwt.JwtKit;
import com.tmzn.mapper.TDevicePlanMapper;
import com.tmzn.mapper.TDeviceRecordMapper;
import com.tmzn.redis.RedisCache;
import com.tmzn.service.DeviceControlerService;
import com.tmzn.service.ITDevicePlanService;
import com.tmzn.service.ITPlanRecordService;
import com.tmzn.transdata.RpcResult;
import com.tmzn.transdata.entity.PlanCharge;
import com.tmzn.util.DateUtil;
import com.tmzn.util.HutoolHttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class TDevicePlanServiceImpl extends ServiceImpl<TDevicePlanMapper, TDevicePlan> implements ITDevicePlanService {

    @Autowired
    TDevicePlanMapper tDevicePlanMapper;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private DeviceControlerService deviceControlerService;

    @Autowired
    private ITPlanRecordService itPlanRecordService;

    @Autowired
    private TDeviceRecordMapper tDeviceRecordMapper;

    @Value("${phpUrl}")
    private String phpUrl;

    @Value("${cancelPlanUrl}")
    private String cancelUrl;

    /**
     * 添加预约
     *
     * @param tDevicePlanAddNewDTO 添加预约
     */
    @Override
    @DataSource(DataSourceType.SLAVE)
    public void addNew(TDevicePlanAddNewDTO tDevicePlanAddNewDTO) {
        log.info("开始处理【添加预约】的业务，参数：{}", tDevicePlanAddNewDTO);
        // 创建TDevicePlan对象
        TDevicePlan tDevicePlan = new TDevicePlan();
        // 调用BeanUtils.copyProperties(源,目标)将参数对象中的属性复制到TDevicePlan对象中
        BeanUtils.copyProperties(tDevicePlanAddNewDTO, tDevicePlan);
        // 调用Mapper对象的insert()执行插入预约数据
        log.info("即将执行插入数据，参数：{}", tDevicePlan);
        tDevicePlan.setStatus(1L);
        tDevicePlan.setUserId(JwtKit.getLoginUser().getId());
        boolean save = this.save(tDevicePlan);
        if (!save) {
            String message = "添加预约失败，服务器忙，请稍后再次尝试！";
            log.warn(message);
            throw new RuntimeException(message);
        }
    }

    /**
     * 检查预约的单子
     *
     * @return
     */
    @Override
    public void checkReadyToRun() {

    }

    private boolean checkHasRun(TDevicePlan tDevicePlan,LocalDateTime now) {
        LambdaQueryWrapper<TPlanRecord> queryWrapper = Wrappers.lambdaQuery();
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String todayStr = now.format(fmt);
        queryWrapper.eq(TPlanRecord::getPlanId, tDevicePlan.getId());
//        queryWrapper.apply("create_time>= '"+todayStr+" 00:00:00'");
        queryWrapper.gt(TPlanRecord::getCreateTime,""+todayStr+" 00:00:00");
        queryWrapper.last(" limit 1");
        TPlanRecord one = itPlanRecordService.getOne(queryWrapper);
        if (one != null) {
            LocalDateTime createTime = one.getCreateTime();


            String runTime = createTime.format(fmt);
            if (StringUtils.equals(runTime, todayStr)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 计划任务每一分钟执行一次，检测预约充电是否成功
     * 若未成功，可以在手动充电一次
     */
    @Override
    public void checkRunSuccess() {
    }

    @Override
    @DataSource(DataSourceType.SLAVE)
    public RpcResult cancelPlan(Long planId) {
        log.info("外部调用取消预约,{}",planId);
        String s = HttpUtil.get(cancelUrl + "/device/cancelPlan/" + planId);
        RpcResult rpcResult = JSONObject.parseObject(s, RpcResult.class);
        log.info("外部调用预约取消,{},{}",planId,s);
        return rpcResult;

    }

    /**
     * 过4分钟的检查任务
     * @param deviceId
     * @param object
     */
    @Override
    public void runPlanChargeJob(String deviceId, JSONObject object) {
        String cancelKey = IotConstant.CANCEL_CHARGE_DIR_REDIS+""+deviceId;
        TDevicePlan tDevicePlan = redisCache.getAndDel(cancelKey);
        if(tDevicePlan !=null){//如果有取消的任务
            redisCache.deleteObject(cancelKey);
            cancelPlanCharge(deviceId,object,tDevicePlan);
            return;
        }
        String redisKey = IotConstant.PLAN_CHARGE_DIR_REDIS_KEY+""+deviceId;
        tDevicePlan = redisCache.getAndDel(redisKey);
        LocalDateTime planTime = LocalDateTime.now();
        if(tDevicePlan != null){//代表预约了任务，需要判断端口状态
            redisCache.deleteObject(redisKey);
            startPlanCharge(deviceId, object, tDevicePlan, planTime);
        }else{
            redisKey = IotConstant.START_CHARGE_DIR_REDIS+""+deviceId;
            TPlanRecord planRecord = redisCache.getAndDel(redisKey);
            if(planRecord != null){
                redisCache.deleteObject(redisKey);
                startCharge(deviceId, object, planRecord);
            }
        }


    }

    @Override
    public void getPortStatus() {
        long runGap = 1000 * 58 * 10L;//执行间隔10分钟一次
        long now = System.currentTimeMillis();
        Map<String, PortData> cacheMap = redisCache.getCacheMap(IotConstant.GET_PORT_CMD_KEY);
        if(cacheMap != null){
            int max = 50;
            int i =0;
            Set<String> deviceIds = cacheMap.keySet();
            for (String deviceId : deviceIds) {
                boolean needRun = false;
                if(i>=max){
                    break;
                }
                PortData portData = cacheMap.get(deviceId);
                Long time = portData.getTime();
                if(time == null){
                    needRun = true;
                }else{
                    long gap = now-time;
                    if(gap>=runGap){
                        needRun = true;
                    }
                }
                if(needRun){
                    portData.setTime(now);
                    operCacheMap(1,deviceId,portData);
                    DeviceParam deviceParam = new DeviceParam();
                    deviceParam.setCcid(portData.getCcid());
                    deviceParam.setDeviceId(deviceId);
                    deviceControlerService.sendPortDetailCmd(deviceParam);
                    i++;
                }
            }
            long end = System.currentTimeMillis();
            log.info("当前端口执行任务完成,执行时间{},设备个数:{},执行数量：{}",(end-now),deviceIds.size(),i);
        }
    }

    @Override
    public void portChange(String deviceId, JSONObject jsonObject) {
        Integer type = jsonObject.getInteger("type");
        JSONArray data = jsonObject.getJSONArray("data");

        Map<String, Object> deviceDataFromOri = tDeviceRecordMapper.
                getDeviceDataFromOri("SELECT id,sn,mac,nominal_master_id,inter_sn,mf_set from ims_shopping_goods  where mac = '" + deviceId + "' and mf_set = 10 limit 1");
        if(deviceDataFromOri == null){
            return;
        }
        Object inter_sn = deviceDataFromOri.get("inter_sn");
        String ccid = inter_sn.toString();
        PortData portData = new PortData();
        portData.setCcid(ccid);
        portData.setTime(0L);
        portData.setDeviceId(deviceId);
        Integer changePort  = 0;
        if(type.equals(116)){//端口变化指令 且状态变为已经使用
            Object o = data.get(4);
            if(o.equals(2)){//需要添加一条充电记录
                operCacheMap(1,deviceId,portData);
                changePort = (Integer)data.get(3);
            }

        }
        if(type.equals(104)){
            operCacheMap(1,deviceId,portData);
        }
        if(type.equals(113)){//需要更新充电记录
            operCacheMap(2,deviceId,null);
        }
        if(changePort>0){
                notityPhp(deviceId,changePort);
        }
    }

    private void notityPhp(String deviceId, Integer changePort) {
        try{
            String key = "weitiandi2023!@!";
            LocalDateTime now = LocalDateTime.now();
            LambdaQueryWrapper<TPlanRecord> queryWrapper = Wrappers.lambdaQuery();
            queryWrapper.eq(TPlanRecord::getDeviceId,deviceId)
                    .eq(TPlanRecord::getPort,changePort)
                    .orderByDesc(TPlanRecord::getCreateTime)
                    .last("limit 1");
            TPlanRecord one = itPlanRecordService.getOne(queryWrapper);
            if(one != null){
                LocalDateTime createTime = one.getCreateTime();
                LocalDateTime plus = createTime.plus(10, ChronoUnit.MINUTES);
                int i = plus.compareTo(now);
                if(i>=0){
                    long time = System.currentTimeMillis();
                    String url = time+""+key;
                    MD5 md5 = MD5.create();
                    String sign = md5.digestHex(url);
                    String allUrl = phpUrl+"/api/dev_notify.php?sign="+sign+"&time="+time+"&mac="+deviceId+"&port="+changePort;
                    String s = HutoolHttpUtil.get(allUrl, new HashMap<>());
                    log.info("notice php,{},{}",allUrl,s);

                }
            }
        }catch (Exception e){
            e.printStackTrace();;
            log.error("请求PHP错误",e);
        }
    }

    /**
     *
     * @param type  1是修改 2是移除
     * @param deviceId  设备ID
     * @param portData
     */
    private synchronized void operCacheMap(int type,String deviceId,PortData portData){
        if(type == 1){//新增
            redisCache.setCacheMapValue(IotConstant.GET_PORT_CMD_KEY,deviceId,portData);
            log.info("添加端口执行队列:{}",deviceId);
        }else if(type == 2){
            redisCache.deleteCacheMapValue(IotConstant.GET_PORT_CMD_KEY,deviceId);
            log.info("移除端口执行队列:{}",deviceId);
        }

    }

    private void cancelPlanCharge(String deviceId, JSONObject object, TDevicePlan tDevicePlan) {
        Integer port = tDevicePlan.getPort();
        String ccid = tDevicePlan.getCcid();
        Integer port_status = null;
        if(port == 1){
            port_status = object.getInteger("port_first_status");
        }else if(port == 2){
            port_status = object.getInteger("port_second_status");
        }
        if(port_status == null){
            port_status = 0;
        }
        if(port_status == 6){
            deviceControlerService.cancelChargePlan(deviceId,ccid,port);
            log.info("端口{}已经预约，发送取消预约指令，{}",port,deviceId);
        }
    }

    /**
     * 手动开始充电
     * @param deviceId
     * @param object
     * @param planRecord
     */
    private void startCharge(String deviceId, JSONObject object, TPlanRecord planRecord) {
        Long planId = planRecord.getPlanId();
        LambdaQueryWrapper<TDevicePlan> objectLambdaQueryWrapper = Wrappers.lambdaQuery();
        objectLambdaQueryWrapper.eq(TDevicePlan::getId,planId);
        Integer port = planRecord.getPort();
        TDevicePlan one = this.getOne(objectLambdaQueryWrapper);
        if(one == null){
            planRecord.setStatus(-4);
            log.info("检测端口：{}预约任务未找到,无法执行{}",port, deviceId);
        }else{
            Long status = one.getStatus();
            if(status.equals(3L)){//代表已经取消
                planRecord.setStatus(-3);
                log.info("检测端口：{}预约任务已取消,未执行{}",port, deviceId);
            }else{
                Integer port_status = null;
                if(port == 1){
                    port_status = object.getInteger("port_first_status");
                }else if(port == 2){
                    port_status = object.getInteger("port_second_status");
                }
                if(port_status == null){
                    port_status = 0;
                }
                if(port_status != 2){
                    String ccid = planRecord.getCcid();
                    String deviceId1 = planRecord.getDeviceId();
                    planRecord.setStatus(-2);
                    deviceControlerService.startCharge(deviceId,ccid,port);
                    log.info("检测端口：{}未在充电--发送充电指令,{}",port, deviceId);
                }else{
                    planRecord.setStatus(3);
                    log.info("检测端口：{}自动充电--已经充电中,{}",port, deviceId);
                }
            }
        }
        itPlanRecordService.saveOrUpdate(planRecord);


    }

    /**
     * 开始计划充电
     * @param deviceId
     * @param object
     * @param tDevicePlan
     * @param planTime
     */
    private void startPlanCharge(String deviceId, JSONObject object, TDevicePlan tDevicePlan, LocalDateTime planTime) {
        LocalDateTime plus = planTime.plus(4, ChronoUnit.MINUTES);
        Long id = tDevicePlan.getId();
        Integer port = tDevicePlan.getPort();
        LambdaQueryWrapper<TDevicePlan> objectLambdaQueryWrapper = Wrappers.lambdaQuery();
        Long planId = tDevicePlan.getId();
        objectLambdaQueryWrapper.eq(TDevicePlan::getId,planId);
        TDevicePlan one = this.getOne(objectLambdaQueryWrapper);
        if(one == null){
            return;
        }
        Long status = one.getStatus();
        if(status.equals(3L)){
            log.info("检测端口：{}，预约任务已取消,未执行{}",port, deviceId);
            return;
        }
        String ccid = tDevicePlan.getCcid();
        Integer port_status = null;
        if(port == 1){
            port_status = object.getInteger("port_first_status");
        }else if(port == 2){
            port_status = object.getInteger("port_second_status");
        }
        if(port_status == null){
            port_status = 0;
        }
        if(port_status == 6){//代表端口已经预约，忽略本次
            log.info("端口{}已经预约，忽略本次预约，{}",port,deviceId);
            return;
        }
        LambdaQueryWrapper<TPlanRecord> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(TPlanRecord::getPlanId, id).orderByDesc(TPlanRecord::getCreateTime).last("limit 1");
        TPlanRecord planRecord = itPlanRecordService.getOne(lambdaQueryWrapper);
        if(port_status == 5){//已经连接
            PlanCharge planCharge = new PlanCharge();
            planCharge.setMoney(50000);
            planCharge.setPort(port);
            planCharge.setTime(3);
            deviceControlerService.planCharge(deviceId,ccid,planCharge);

            planRecord.setStatus(1);
            planRecord.setPlanTime(planTime);
            planRecord.setCheckTime(plus);
            itPlanRecordService.saveOrUpdate(planRecord);
            log.info("端口{}已经连接，发送预约充电指令，{}",port,deviceId);
        }else if(port_status != 6){//
            planRecord.setStatus(-1);
            planRecord.setCheckResult("端口状态为:"+port_status+",无法充电");
            itPlanRecordService.saveOrUpdate(planRecord);
            log.info("端口{}状态不是已连接,端口状态 ：{},无法发送完成预约充电，{}",port,port_status,deviceId);
        }
    }


}
